using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Windows.Forms;

using Animation;
using Rigging;

namespace Skeletons
{
    // Il sistema di Undo/Redo funziona essenzialmente come due stack, uno con le Azioni da annullare, dove man mano
    // che l'utente lavora vengono pushate le chiavi dell'operazione (o addirittura un backup dello stato precedente)
    // L'altro stack, di Redo, normalmente vuoto, si popola quando l'utente torna indietro annullando azioni

    // Action types for the Action class
    public enum ActionType { ActionSkeleton, ActionAnimation, ActionRigging };
    
    class UndoManager
    {
        private Skeleton m_backup;
        private Skeleton m_workSkel;
        private Animator m_workAnim;
        private SkinLoader m_workSkins;

        //private TreeView m_treeView;
        //private ListView m_undoList;
        private Form1    m_mainForm;

        private Stack<Action> m_undo = new Stack<Action>();
        private Stack<Action> m_redo = new Stack<Action>();
        private ActionType m_type;

        public UndoManager(Animator anim, Skeleton skel, Form1 mainForm)
        {
            m_workAnim = anim;
            m_workSkel = skel;
            //m_treeView = tv;
            //m_undoList = undolist;
            m_mainForm = mainForm;
        }

        public bool UndoLastAction()
        {
            // attenzione, l'azione in fondo allo stack non dev'essere annullata MAI

            if (m_undo.Count > 1)
            {

                // dovrebbe pop-are dallo stack lo stato prima dell'Action
                Action act = null;

                // il comando sotto sposta l'action dalla pila delle cose fatte nella pila delle cose annullate
                m_redo.Push(m_undo.Pop());
                
                act = m_undo.Peek();


                // ricarica lo scheletro corrente a partire dai dati nello stack Undo
                XmlNode dati = null;
                if (act.Type == ActionType.ActionSkeleton)
                {
                    // resetta e carica da zero il treeview sulla sinistra
                    m_mainForm.treeView1.Nodes.Clear();
                    m_mainForm.treeView1.Nodes.Add("Skeleton");
                    // ricarica lo scheletro dal testo xml
                    m_workSkel.LoadFromXml(act.XmlData.SelectSingleNode("//SKELETON"), m_mainForm.treeView1);
                }
                else if (act.Type == ActionType.ActionAnimation)
                {
                    m_workAnim.LoadFromXml(act.XmlData.SelectSingleNode("//ANIMATION"));
                }
                else if (act.Type == ActionType.ActionRigging)
                {
                    if (m_workSkins == null)
                        m_workSkins = new SkinLoader();
                    m_workSkins.LoadSkins(m_workSkel, act.XmlData.SelectSingleNode("//RIGGING"));
                }

                // infine toglie la prima voce dalla listView che l'utente vede
                m_mainForm.listUndo.Items.RemoveAt(0);

                // aggiorna il treeview (espandi)
                m_mainForm.treeView1.ExpandAll();

                // setta il testo del pulsante Redo
                m_mainForm.toolStripRedo.Enabled = true;


                // se non ci sono altre azioni nello stack, disabilita il pulsante
                if (m_undo.Count == 1)  // 1 = ultima
                {
                    m_mainForm.toolStripUndo.ToolTipText = m_undo.Peek().Description;
                    m_mainForm.toolStripUndo.Enabled = false;
                }
                else
                {
                    m_mainForm.toolStripUndo.ToolTipText = "Annulla '" + m_undo.Peek().Description + "'";
                }

                m_mainForm.toolStripRedo.ToolTipText = "Ripeti '" + m_redo.Peek().Description + "'";
            }
            else
                MessageBox.Show("Errore: non ci sono azioni da annullare!", "Errore", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

            return true;
        }

        public void RegisterAction(string desc, ActionType type)
        {
            m_type = type;
            switch (type)
            {
                case ActionType.ActionSkeleton:
                    m_undo.Push(new Action(type, desc, m_workSkel.ToXmlString("")));
                    break;
                case ActionType.ActionAnimation:
                    m_undo.Push(new Action(type, desc, m_workAnim.ToXmlString("")));
                    break;
                case ActionType.ActionRigging:
                    m_undo.Push(new Action(type, desc, "<RIGGING>\n" + m_workSkel.RiggingToXmlString("") + "</RIGGING>"));
                    break;
            }
            m_mainForm.listUndo.Items.Insert(0, desc);
            if (m_undo.Count > 1)
            {
                m_mainForm.toolStripUndo.ToolTipText = "Annulla '" + desc + "'";
                m_mainForm.toolStripUndo.Enabled = true;
            }
         }

        public bool RedoFirstAction()
        {
            if (m_redo.Count > 0)
            {
                // sposta l'azione in cima (a Redo), di nuovo nello stack Undo
                Action act = null;
                m_undo.Push(act = m_redo.Pop());

                // resetta e carica da zero il treeview sulla sinistra
                m_mainForm.treeView1.Nodes.Clear();
                m_mainForm.treeView1.Nodes.Add("Skeleton");

                // ricarica lo scheletro corrente a partire dai dati nello stack Undo
                XmlNode dati = null;
                if (act.Type == ActionType.ActionSkeleton)
                    m_workSkel.LoadFromXml(act.XmlData.SelectSingleNode("//SKELETON"), m_mainForm.treeView1);
                else if (act.Type == ActionType.ActionAnimation)
                    m_workAnim.LoadFromXml(act.XmlData.SelectSingleNode("//ANIMATION"));

                // infine rimette la prima voce dalla listView che l'utente vede
                m_mainForm.listUndo.Items.Insert(0, act.Description);

                // aggiorna il treeview (espandi)
                m_mainForm.treeView1.ExpandAll();

                // setta il testo del pulsante Undo
                m_mainForm.toolStripUndo.Enabled = true;

                // se non ci sono altre azioni nello stack, disabilita il pulsante
                if (m_redo.Count == 0)  
                {
                    m_mainForm.toolStripRedo.ToolTipText = "(nulla da ripetere)";
                    m_mainForm.toolStripRedo.Enabled = false;
                }
                else
                {
                    m_mainForm.toolStripRedo.ToolTipText = "Ripeti '" + m_redo.Peek().Description + "'";
                }

                m_mainForm.toolStripUndo.ToolTipText = "Annulla '" + m_undo.Peek().Description + "'";
            }
            else
                MessageBox.Show("Errore: non ci sono azioni da ripetere!", "Errore", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

            return true;
        }
    }

    public class Action
    {
        private string m_description;

        public string Description
        {
            get { return m_description; }
            set { m_description = value; }
        }
        private ActionType m_type;

        public ActionType Type
        {
            get { return m_type; }
            set { m_type = value; }
        }
        private string m_keys;
        private XmlNode m_xmlData;
        private object m_object;
        private XmlDocument m_doc = new XmlDocument();

        public XmlNode XmlData
        {
            get { return m_xmlData; }
            set { m_xmlData = value; }
        }
        
        public Action(ActionType type, string desc, string data)
        {
            m_description = desc;
            m_type = type;
            m_doc.LoadXml(data);
            XmlNode nd = null;
            if (type == ActionType.ActionSkeleton)
                nd = m_doc.SelectSingleNode("SKELETON");
            else if (type == ActionType.ActionAnimation)
                nd = m_doc.SelectSingleNode("?");
            else if (type == ActionType.ActionRigging)
                nd = m_doc.SelectSingleNode("RIGGING");
            m_xmlData = nd;
        }

    }

    // TODO: Remove this class, if unused
    public class SkeletonProject
    {
        public SkeletonProject(Skeleton[] skels, Animator anim)
        { 
        }


    }


}
